package com.wei.czz.framework.common.handler.redis;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2021-08-12 10:30:00
 * className: RedisSetHelper Redis-Set数据结构操作工具类
 * version: 1.0
 * description:
 */
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisSetHandler {

    private static final Logger log = LoggerFactory.getLogger(RedisSetHandler.class);

    private final SetOperations setOperations;

    @Autowired
    public RedisSetHandler(RedisTemplate redisTemplate) {
        this.setOperations = redisTemplate.opsForSet();
    }

    /**
     * 操作set数据，向key指向数据中添加一个或者多个value值
     * 注意：
     *  1、向key空间添加值，可以同时存在添加成功和添加失败两种结果
     *    1.1、比如：key=[1,2,3]， 添加值values=[2,3,4]，则成功添加元素个数为1，只有4添加成功，2和3原来已经存在，不需要重复添加
     * @param key    Set空间键
     * @param values 一个或者多个添加值
     * @param <T>    泛型类型
     * @return 成功向key空间添加元素的个数
     */
    public <T> Long add(final String key, T... values) {
        isBlank(key);
        isEmptyArray("数组没有任何元素，无法进行添加操作!", values);
        log.info("Set类型 add操作 key={} values={}", key, Arrays.toString(values));
        return setOperations.add(key, values);
    }

    /**
     * 解释同上
     */
    public <T> Long add(final String key, Collection<T> values) {
        isBlank(key);
        isEmptyCollection(values, "集合没有任何元素，无法进行添加操作!");
        Object[] array = values.toArray();
        log.info("Set类型 add操作 key={} values={} valueType={}", key, values, array[0].getClass());
        return setOperations.add(key, array);
    }

    /**
     * 操作set数据，从key指向数据中’随机‘删除并获取一个value值
     * 注意：
     *  1、如果key指向数据为空，则返回null
     * @param key Set空间键
     * @param <T> 泛型类型
     * @return 从key指向数据中随机获取的值
     */
    public <T> T pop(final String key) {
        isBlank(key);
        log.info("Set类型 pop操作 key={}", key);
        return (T) setOperations.pop(key);
    }

    /**
     * 操作set数据，从key指向数据中’随机‘删除并获取size个value值
     * @param key  Set空间键
     * @param size 删除并获取的元素个数
     * @param <T>  泛型类型
     * @return size个value值
     */
    public <T> List<T> pop(final String key, final long size) {
        isBlank(key);
        lessOrEqualZero(size, "弹出的元素个数不能小于等于零！");
        log.info("Set类型 pop操作 key={} size={}", key, size);
        return setOperations.pop(key, size);
    }

    /**
     * 操作set数据，获取key指向数据的全部值
     * @param key Set空间键
     * @param <T> 泛型类型
     * @return key指向数据的全部值
     */
    public <T> Set<T> members(final String key) {
        isBlank(key);
        log.info("Set类型 members操作 key={}", key);
        return setOperations.members(key);
    }

    /**
     * 操作set数据，key指向数据是否包含value值
     * @param key   Set空间键
     * @param value 被判断的值
     * @param <T>   泛型类型
     * @return 包含返回true，不包含返回false
     */
    public <T> Boolean isMember(final String key, final T value) {
        isBlank(key);
        isEmptyArray("value值不能为空", value);
        log.info("Set类型 isMember操作 key={} value={}", key, value);
        return setOperations.isMember(key, value);
    }

    /**
     * 操作set数据，从key指向数据中随机获取一个元素，但是不删除元素
     * 注意：
     *  1、如果key指向空间不存在，则返回null
     * @param key Set空间键
     * @param <T> 泛型类型
     * @return 随机获取到的元素
     */
    public <T> T randomMember(final String key) {
        log.debug("Set类型 randomMember操作 key={}", key);
        return (T) setOperations.randomMember(key);
    }

    /**
     * 操作set数据，从key指向数据中随机获取size个元素，但是不删除元素
     * 注意：
     *  1、
     * @param key  Set空间键
     * @param size 获取的元素个数
     * @param <T>  泛型类型
     * @return 获取到的元素列表
     */
    public <T> List<T> randomMembers(final String key, final Long size) {
        log.debug("Set类型 randomMember操作 key={}", key);
        return setOperations.randomMembers(key, size);
    }

    /**
     * 操作set数据，从key指向数据中删除一个或者多个value值
     * 注意：
     *  1、从key指向数据中删除所有value值，同时存在删除成功和失败两种结果
     *    1.1、只有key指向数据包含待删除的value值，才将value从数据中删除，并使成功计数器加一
     *    1.2、如果key指向数据不包含待删除的value值，则删除失败，计数器不变
     * @param key    Set空间键
     * @param values 等待删除的所有value值
     * @param <T>    泛型类型
     * @return 成功删除元素个数
     */
    public <T> Long remove(final String key, final T... values) {
        isBlank(key);
        isEmptyArray("参数values不能为空或者元素个数为零!", values);
        log.info("Set类型 remove操作 key={} values={}", key, Arrays.toString(values));
        return setOperations.remove(key, values);
    }

    /**
     * 操作set数据，从key指向数据中删除一个或者多个value值
     * 注意：
     *  1、从key指向数据中删除所有value值，同时存在删除成功和失败两种结果
     *    1.1、只有key指向数据包含待删除的value值，才将value从数据中删除，并使成功计数器加一
     *    1.2、如果key指向数据不包含待删除的额value值，则删除失败，计数器不变
     * @param key    Set空间键
     * @param values 等待删除的所有value值
     * @param <T>    泛型类型
     * @return 成功删除元素个数
     */
    public <T> Long remove(final String key, final Collection<T> values) {
        log.info("Set类型 remove操作 key={} values={}", key, values);
        return setOperations.remove(key, values.toArray());
    }

    /**
     * 操作set数据，获取key指向数据包含的元素个数
     * @param key Set空间键
     * @return key指向数据包含的元素个数
     */
    public Long size(final String key) {
        log.info("Set类型 size操作 key={}", key);
        return setOperations.size(key);
    }

    /*
        以下全部方法为Set类型自带的交、并、补集操作
        涉及到’泛型设计‘，开发者进行如上操作时，应确保两个集合的数据类型一致，否则可能发生各种未知异常
     */

    /**
     * 操作set数据，对key1指向数据和key2指向数据进行取’交集‘操作
     * @param key1 Set空间键
     * @param key2 Set空间键
     * @param <T>  泛型操作
     * @return 返回key1指向数据和key2指向数据取’交集‘结果
     */
    public <T> Set<T> intersect(final String key1, final String key2) {
        log.info("Set类型 intersect操作 keys1={} keys2={}", key1, key2);
        return setOperations.intersect(key1, key2);
    }

    /**
     * 操作set数据，keys包含的所有key指向数据进行取’交集‘操作
     * 注意:
     *  1、如果其中一个key的数据类型与其他key的数据类型不一致，则没有相交元素，返回没有元素的空集合
     *  2、keys如果值包含一个Set空间键，则返回该Set空间键的指向数据
     *  3、如果keys中的键在Redis中不存在，则整个结果为空集合
     * @param keys 所有进行交集操作的Set空间键
     * @param <T>  泛型类型
     * @return 所有key指向数据进行交集后的结果
     */
    public <T> Set<T> intersect(Collection<String> keys) {
        log.info("Set类型 intersect操作 keys={}", keys);
        return setOperations.intersect(keys);
    }

    /**
     * 解释同上
     * 本方法相当于: keys.add(key)，然后调用setOperations.intersect(keys)方法
     */
    public <T> Set<T> intersect(final String key, Collection<String> keys) {
        log.info("Set类型 intersect操作 keys={} keys={}", key, keys);
        return setOperations.intersect(key, keys);
    }

    /**
     * 操作set数据，对key1指向数据和key2指向数据进行取’交集‘操作，并将操作结果存入resultKey指向数据中
     * 注意:
     *  1、如果resultKey键存在
     *   1.1、交集结果不为空，结果将覆盖旧的数据（删除所有旧数据）
     *   1.2、交集结果为空，删除resultKey指向的所有数据
     *  2、如果resultKey键不存在，且交集结果不为空，创建一个新键resultKey，并增加其指向的数据
     * @param key1      Set空间键
     * @param key2      Set空间键
     * @param resultKey 交集结果Set空间键
     * @return 交集结果集合元素个数
     */
    public Long intersectAndStore(final String key1, final String key2, final String resultKey) {
        log.info("Set类型 intersectAndStore操作 keys1={} keys2={} resultKey={}", key1, key2, resultKey);
        return setOperations.intersectAndStore(key1, key2, resultKey);
    }

    /**
     * 操作set数据，keys包含的所有key指向数据进行取’交集‘操作，并将操作结果存入resultKey指向数据中
     * 注意:
     *  1、如果其中一个key的数据类型与其他key的数据类型不一致，则没有相交元素，返回没有元素的空集合
     *  2、keys如果值包含一个Set空间键，则将该Set空间键的指向数据添加到resultKey指向的数据中
     *  3、如果keys中的键在Redis中不存在，则整个结果为空集合，并没有元素写入resultKey指向数据中
     * @param keys      所有进行交集的Set空间键
     * @param resultKey 交集结果Set空间键
     * @return 交集结果集合元素个数
     */
    public Long intersectAndStore(Collection<String> keys, final String resultKey) {
        log.info("Set类型 intersectAndStore操作 keys={} resultKey={}", keys, resultKey);
        return setOperations.intersectAndStore(keys, resultKey);
    }

    /**
     * 解释同上
     * 本方法相当于: keys.add(key)，然后调用setOperations.intersectAndStore(keys, resultKey)方法
     * @param resultKey 交集结果Set空间键
     * @return 交集结果集合元素个数
     */
    public Long intersectAndStore(final String key, Collection<String> keys, final String resultKey) {
        log.info("Set类型 intersectAndStore操作 key={} keys={} resultKey={}", key, keys, resultKey);
        return setOperations.intersectAndStore(key, keys, resultKey);
    }

    /*
        Set数据类型去‘并集’操作注意事项（key1、keys指代下方的方法参数）
        1、如果key1和key2的指向数据类型不一致，则返回key1和key2指向的全部数据。
          1.1、开发者应确认操作的两个key指向的数据类型
          1.2、实际操作中不应对数据类型不一致的两个key进行并集操作
        2、keys如果值包含一个Set空间键，则返回该Set空间键的指向数据
        3、如果key1和key2键在Redis中不存在，则返回空集合
        4、对key1和key2进行并集操作，并将结果存储resultKey中
          4.1、如果resultKey键存在
             4.1.1、并集结果不为空，结果将覆盖旧的数据（删除所有旧数据）
             4.1.2、并集结果为空，删除resultKey指向的所有数据
          4.2、如果resultKey键不存在，且并集结果不为空，创建一个新键resultKey，并增加其指向的数据
     */

    /**
     * 操作set数据，对key1指向数据和key2指向数据进行取’并集‘操作
     * @param key1 Set空间键
     * @param key2 Set空间键
     * @param <T>  泛型操作
     * @return 返回key1指向数据和key2指向数据取’并集‘结果
     */
    public <T> Set<T> union(final String key1, final String key2) {
        log.info("Set类型 union操作 keys1={} keys2={}", key1, key2);
        return setOperations.union(key1, key2);
    }

    /**
     * 操作set数据，keys包含的所有key指向数据进行取’并集‘操作
     * @param keys 所有进行并集操作的Set空间键
     * @param <T>  泛型类型
     * @return 所有key指向数据进行并集后的结果
     */
    public <T> Set<T> union(Collection<String> keys) {
        log.info("Set类型 union操作 keys={}", keys);
        return setOperations.union(keys);
    }

    /**
     * 解释同上
     * 本方法相当于: keys.add(key)，然后调用setOperations.intersect(keys)方法
     */
    public <T> Set<T> union(final String key, Collection<String> keys) {
        log.info("Set类型 union操作 keys={} keys={}", key, keys);
        return setOperations.union(key, keys);
    }

    /**
     * 操作set数据，对key1指向数据和key2指向数据进行取’并集‘操作，并将操作结果存入resultKey指向数据中
     * @param key1      Set空间键
     * @param key2      Set空间键
     * @param resultKey 交集结果Set空间键
     * @return 并集操作结果集合元素个数
     */
    public Long unionAndStore(final String key1, final String key2, final String resultKey) {
        log.info("Set类型 unionAndStore操作 keys1={} keys2={} resultKey={}", key1, key2, resultKey);
        return setOperations.unionAndStore(key1, key2, resultKey);
    }

    /**
     * 操作set数据，keys包含的所有key指向数据进行取’并集‘操作，并将操作结果存入resultKey指向数据中
     * @param keys      所有进行交集的Set空间键
     * @param resultKey 并集结果Set空间键
     * @return 并集操作结果集合元素个数
     */
    public Long unionAndStore(Collection<String> keys, final String resultKey) {
        log.info("Set类型 unionAndStore操作 keys={} resultKey={}", keys, resultKey);
        return setOperations.unionAndStore(keys, resultKey);
    }

    /**
     * 解释同上
     * 本方法相当于: keys.add(key)，然后调用setOperations.intersectAndStore(keys, resultKey)方法
     * @param resultKey 并集结果Set空间键
     * @return 并集操作结果集合元素个数
     */
    public Long unionAndStore(final String key, Collection<String> keys, final String resultKey) {
        log.info("Set类型 unionAndStore操作 key={} keys={} resultKey={}", key, keys, resultKey);
        return setOperations.unionAndStore(key, keys, resultKey);
    }

    /*
        Set数据类型去‘补集’操作注意事项（key1、keys指代下方的方法参数）
        1、key1或者keys的第一个key作为基准集合，key2或者keys除第一个以外的key作为差集计算的条件集合
          1.1、key指向数据的元素，在其他所有key指向数据中存在，即为不合格元素
          1.2、key指向数据的元素，在其他所有key指向数据中不存在，即为合格元素
        2、keys如果值包含一个Set空间键，则返回该Set空间键的指向数据
        3、对key1和key2进行差集操作，并将结果存储resultKey中
          3.1、如果resultKey键存在
             3.1.1、差集结果不为空，结果将覆盖旧的数据（删除所有旧数据）
             3.1.2、差集结果为空，删除resultKey指向的所有数据
          3.2、如果resultKey键不存在，且差集结果不为空，创建一个新键resultKey，并增加其指向的数据

     */

    /**
     * 操作set数据，对key1指向数据和key2指向数据进行取’差集‘操作
     * @param key1 Set空间键
     * @param key2 Set空间键
     * @param <T>  泛型操作
     * @return 返回key1指向数据和key2指向数据取’差集‘结果
     */
    public <T> Set<T> difference(final String key1, final String key2) {
        log.info("Set类型 difference操作 keys1={} keys2={}", key1, key2);
        return setOperations.difference(key1, key2);
    }

    /**
     * 操作set数据，keys包含的所有key指向数据进行取’差集‘操作
     * @param keys 所有进行并集操作的Set空间键
     * @param <T>  泛型类型
     * @return 所有key指向数据进行差集后的结果
     */
    public <T> Set<T> difference(Collection<String> keys) {
        log.info("Set类型 difference操作 keys={}", keys);
        return setOperations.difference(keys);
    }

    /**
     * 解释同上
     * 本方法相当于: keys.add(key)，然后调用setOperations.intersect(keys)方法
     */
    public <T> Set<T> difference(final String key, Collection<String> keys) {
        log.info("Set类型 difference操作 keys={} keys={}", key, keys);
        return setOperations.difference(key, keys);
    }

    /**
     * 操作set数据，对key1指向数据和key2指向数据进行取’差集‘操作，并将操作结果存入resultKey指向数据中
     * @param key1      Set空间键
     * @param key2      Set空间键
     * @param resultKey 交集结果Set空间键
     * @return 差集操作结果集合元素个数
     */
    public Long differenceAndStore(final String key1, final String key2, final String resultKey) {
        log.info("Set类型 differenceAndStore操作 keys1={} keys2={} resultKey={}", key1, key2, resultKey);
        return setOperations.differenceAndStore(key1, key2, resultKey);
    }

    /**
     * 操作set数据，keys包含的所有key指向数据进行取’差集‘操作，并将操作结果存入resultKey指向数据中
     * @param keys      所有进行交集的Set空间键
     * @param resultKey 并集结果Set空间键
     * @return 差集操作结果集合元素个数
     */
    public Long differenceAndStore(Collection<String> keys, final String resultKey) {
        log.info("Set类型 differenceAndStore操作 keys={} resultKey={}", keys, resultKey);
        return setOperations.differenceAndStore(keys, resultKey);
    }

    /**
     * 解释同上
     * 本方法相当于: keys.add(key)，然后调用setOperations.intersectAndStore(keys, resultKey)方法
     * @param resultKey 并集结果Set空间键
     * @return 差集操作结果集合元素个数
     */
    public Long differenceAndStore(final String key, Collection<String> keys, final String resultKey) {
        log.info("Set类型 differenceAndStore操作 key={} keys={} resultKey={}", key, keys, resultKey);
        return setOperations.differenceAndStore(key, keys, resultKey);
    }


    /**
     * 判断传入的Key是否为空
     * @param key 参数key
     */
    private void isBlank(String key) {
        if (key == null)
            throw new RuntimeException("参数key不能为空！");
        int strLen = key.length();
        if (strLen == 0)
            throw new RuntimeException("参数key字符个数不能为0！");
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(key.charAt(i))) {
                return;
            }
        }
        throw new RuntimeException("参数key不能为空白字符串!");
    }

    /**
     * 判断size是否小于等于0
     * @param size 数值
     */
    private void lessOrEqualZero(long size, String message) {
        if (size <= 0) {
            throw new RuntimeException(message);
        }
    }

    /**
     * 判断数组是否为空或者数组元素个数为零
     * @param message 错误提示消息
     * @param values  校验的数组
     * @param <T>     泛型参数
     */
    private <T> void isEmptyArray(String message, T... values) {
        if (values == null || values.length == 0) {
            throw new RuntimeException(message);
        }
    }

    /**
     * 判断集合是否为空或者集合元素个数为零
     * @param values  校验的集合
     * @param message 错误提示消息
     * @param <T>     泛型参数
     */
    private <T> void isEmptyCollection(Collection<T> values, String message) {
        if (values == null || values.size() == 0) {
            throw new RuntimeException(message);
        }
    }
}
